<template>
  <Teleport to="body">
    <Transition name="kaitify-bubble" @before-enter="onShow" @enter="onShowing" @after-enter="onShown" @before-leave="onHide" @leave="onHiding" @after-leave="onHidden">
      <div v-if="shouldVisible" v-bind="attrs" ref="elRef" class="kaitify-bubble" :class="{ 'kaitify-dark': dark }">
        <slot></slot>
      </div>
    </Transition>
  </Teleport>
</template>
<script setup lang="ts">
import { ref, inject, watch, getCurrentInstance, onBeforeUnmount, computed, onMounted, Ref, useAttrs } from 'vue'
import { createPopper, Instance } from '@popperjs/core'
import { event as DapEvent } from 'dap-util'
import { StateType } from '../wrapper'
import { BubbleEmitsType, BubblePropsType } from './props'

defineOptions({
  name: 'Bubble',
  inheritAttrs: false
})
const instance = getCurrentInstance()!
const attrs = useAttrs()
//属性
const props = withDefaults(defineProps<BubblePropsType>(), {
  visible: false
})
//事件
const emits = defineEmits<BubbleEmitsType>()
//是否深色模式
const dark = inject<Ref<boolean>>('dark')!
//编辑器状态数据
const state = inject<Ref<StateType>>('state')!
const disabled = inject<boolean>('disabled')!
const isMouseDown = inject<Ref<boolean>>('isMouseDown')!
const wrapperRef = inject<Ref<HTMLElement | undefined>>('elRef')!
//popperjs实例
const popperInstance = ref<Instance>()
//气泡元素
const elRef = ref<HTMLElement>()

//是否显示气泡栏
const shouldVisible = computed(() => {
  if (disabled) {
    return false
  }
  if (isMouseDown.value && props.hideOnMousedown) {
    return false
  }
  return props.visible ?? false
})

//销毁popperjs实例
const destroyPopperjs = () => {
  if (popperInstance.value) {
    popperInstance.value.destroy()
    popperInstance.value = undefined
  }
}
//获取编辑器内的光标位置
const getVirtualDomRect = () => {
  if (!state.value.editor || !wrapperRef.value) {
    return null
  }
  if (state.value.editor.selection.focused()) {
    if (props.match) {
      const node = state.value.editor.getMatchNodeBySelection(props.match)
      if (node) {
        const dom = state.value.editor.findDom(node)
        return dom.getBoundingClientRect()
      }
    }
    const selection = window.getSelection()
    if (!selection || !selection.rangeCount) return wrapperRef.value.getBoundingClientRect()
    const range = selection.getRangeAt(0)
    const rects = range.getClientRects()
    if (rects.length) {
      const rect = rects[rects.length - 1]
      return {
        top: rect.top,
        left: rect.left,
        right: rect.left + rect.width,
        bottom: rect.top + rect.height,
        width: rect.width,
        height: rect.height,
        x: rect.left,
        y: rect.top,
        toJSON: () => {}
      } as DOMRect
    }
  }
  return wrapperRef.value.getBoundingClientRect()
}
//更新气泡位置
const updatePosition = () => {
  if (!shouldVisible.value || !elRef.value || !state.value.editor) {
    return
  }
  const domRect = getVirtualDomRect()!
  //销毁当前popperjs实例
  destroyPopperjs()
  //重新创建popperjs实例
  popperInstance.value = createPopper(
    {
      getBoundingClientRect: () => domRect
    },
    elRef.value,
    {
      placement: 'bottom-start',
      modifiers: [
        //控制浮层的位置计算方式，包括使用 GPU 加速、是否启用自适应等
        {
          name: 'computeStyles',
          options: {
            adaptive: true, //启用自适应
            gpuAcceleration: false //关闭GPU加速
          }
        },
        //如果弹出框在预设的位置被页面边界或其他限制遮挡，popperjs会自动尝试翻转到其他位置。它会检查可用的视窗空间并自动调整位置，确保内容不会超出视窗或被遮挡。
        {
          name: 'flip',
          options: {
            enabled: true,
            fallbackPlacements: ['bottom', 'bottom-end', 'top-start', 'top', 'top-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']
          }
        },
        //控制offset为0
        {
          name: 'offset',
          options: {
            offset: [0, 5]
          }
        },
        //确保浮层不会超出指定的边界区域，通常用于当浮层过大或目标位置变化时自动修正浮层位置
        {
          name: 'preventOverflow',
          options: {
            enabled: true,
            boundary: 'viewport',
            padding: 5
          }
        }
      ]
    }
  )
}

//滚动监听
const onScroll = (el: HTMLElement) => {
  DapEvent.on(el, `scroll.kaitify_bubble_${instance.uid}`, () => {
    if (shouldVisible.value) {
      updatePosition()
    }
  })
  if (el.parentNode) {
    onScroll(el.parentNode as HTMLElement)
  }
}
//移除滚动监听
const removeScroll = (el: HTMLElement) => {
  DapEvent.off(el, `scroll.kaitify_bubble_${instance.uid}`)
  if (el.parentNode) {
    removeScroll(el.parentNode as HTMLElement)
  }
}
//气泡栏显示前
const onShow = (el: Element) => {
  emits('show', el as HTMLDivElement)
}
//气泡栏显示时
const onShowing = (el: Element) => {
  updatePosition()
  emits('showing', el as HTMLDivElement)
}
//气泡栏显示后
const onShown = (el: Element) => {
  emits('shown', el as HTMLDivElement)
}
//气泡栏隐藏前
const onHide = (el: Element) => {
  emits('hide', el as HTMLDivElement)
}
//气泡栏隐藏时
const onHiding = (el: Element) => {
  emits('hiding', el as HTMLDivElement)
}
//气泡栏隐藏后
const onHidden = (el: Element) => {
  destroyPopperjs()
  emits('hidden', el as HTMLDivElement)
}

//监听光标变化
watch(
  () => state.value.selection,
  () => {
    //更新气泡位置
    updatePosition()
  },
  {
    immediate: true,
    deep: true
  }
)

onMounted(() => {
  //设置滚动监听
  if (wrapperRef.value) {
    onScroll(wrapperRef.value)
  }
})

onBeforeUnmount(() => {
  destroyPopperjs()
  //wrapperRef.value可能是null，需要从editor.$el取值
  if (wrapperRef.value || state.value.editor?.$el) {
    removeScroll(wrapperRef.value || state.value.editor!.$el!)
  }
})

defineExpose({
  elRef,
  popperInstance
})
</script>
<style src="./style.less" scoped></style>
